<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>OpenLayers绘图工具封装</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
  <style>
    :root {
      --primary-color: #4361ee;
      --secondary-color: #3f37c9;
      --danger-color: #e63946;
      --success-color: #2a9d8f;
      --light-color: #f8f9fa;
      --dark-color: #212529;
    }

    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background-color: #f5f7fa;
      margin: 0;
      padding: 0;
      height: 100vh;
    }

    .app-container {
      display: flex;
      height: 100vh;
      overflow: hidden;
    }

    .toolbar {
      width: 280px;
      background: linear-gradient(135deg, #2c3e50, #1a1a2e);
      color: white;
      padding: 20px;
      display: flex;
      flex-direction: column;
      box-shadow: 3px 0 15px rgba(0, 0, 0, 0.2);
      z-index: 10;
    }

    .toolbar-header {
      text-align: center;
      padding-bottom: 20px;
      border-bottom: 1px solid rgba(255, 255, 255, 0.1);
      margin-bottom: 20px;
    }

    .tool-section {
      background: rgba(255, 255, 255, 0.1);
      border-radius: 10px;
      padding: 15px;
      margin-bottom: 20px;
    }

    .tool-section h5 {
      display: flex;
      align-items: center;
      margin-bottom: 15px;
      color: #64b5f6;
    }

    .tool-section h5 i {
      margin-right: 10px;
      font-size: 1.2rem;
    }

    .btn-group-vertical {
      width: 100%;
    }

    .btn-tool {
      display: flex;
      align-items: center;
      padding: 10px 15px;
      margin-bottom: 8px;
      text-align: left;
      background: rgba(255, 255, 255, 0.08);
      border: none;
      color: white;
      border-radius: 8px;
      transition: all 0.3s;
    }

    .btn-tool:hover {
      background: rgba(255, 255, 255, 0.15);
      transform: translateY(-2px);
    }

    .btn-tool.active {
      background: var(--primary-color);
      box-shadow: 0 4px 8px rgba(67, 97, 238, 0.3);
    }

    .btn-tool i {
      margin-right: 10px;
      font-size: 1.2rem;
    }

    .map-container {
      flex: 1;
      position: relative;
      background: #e9ecef;
    }

    #map {
      width: 100%;
      height: 100%;
    }

    .info-panel {
      position: absolute;
      bottom: 20px;
      left: 20px;
      background: rgba(255, 255, 255, 0.9);
      border-radius: 10px;
      padding: 15px;
      box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
      width: 300px;
      z-index: 5;
    }

    .info-panel h5 {
      color: var(--primary-color);
      margin-bottom: 10px;
      border-bottom: 1px solid #eee;
      padding-bottom: 8px;
    }

    .feature-list {
      max-height: 200px;
      overflow-y: auto;
      margin-bottom: 10px;
    }

    .feature-item {
      display: flex;
      justify-content: space-between;
      padding: 8px 0;
      border-bottom: 1px solid #f1f1f1;
    }

    .feature-item:last-child {
      border-bottom: none;
    }

    .feature-actions i {
      cursor: pointer;
      margin-left: 8px;
      color: #6c757d;
      transition: color 0.2s;
    }

    .feature-actions i:hover {
      color: var(--danger-color);
    }

    .status-bar {
      position: absolute;
      bottom: 0;
      left: 0;
      width: 100%;
      background: rgba(0, 0, 0, 0.7);
      color: white;
      padding: 8px 20px;
      font-size: 0.9rem;
      z-index: 5;
    }

    .export-section {
      margin-top: auto;
      padding-top: 20px;
      border-top: 1px solid rgba(255, 255, 255, 0.1);
    }

    .btn-export {
      background: var(--success-color);
      border: none;
      width: 100%;
      padding: 12px;
      font-weight: 500;
      border-radius: 8px;
      transition: all 0.3s;
    }

    .btn-export:hover {
      background: #21867a;
      transform: translateY(-2px);
      box-shadow: 0 4px 10px rgba(42, 157, 143, 0.4);
    }

    .icon-badge {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      width: 24px;
      height: 24px;
      background: rgba(255, 255, 255, 0.2);
      border-radius: 50%;
      margin-right: 10px;
    }
  </style>
  <link rel="stylesheet"
    href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.15.1/css/ol.css">
  <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.15.1/build/ol.js"></script>
</head>

<body>
  <div class="app-container">
    <!-- 左侧工具栏 -->
    <div class="toolbar">
      <div class="toolbar-header">
        <h4><i class="bi bi-geo-alt"></i> OpenLayers 绘图工具</h4>
        <p class="text-muted">封装点、线、面绘制与编辑功能</p>
      </div>

      <div class="tool-section">
        <h5><i class="bi bi-pencil"></i> 绘图工具</h5>
        <div class="btn-group-vertical">
          <button id="draw-point" class="btn btn-tool">
            <span class="icon-badge"><i class="bi bi-dot"></i></span>绘制点
          </button>
          <button id="draw-line" class="btn btn-tool">
            <span class="icon-badge"><i class="bi bi-slash-lg"></i></span>绘制线
          </button>
          <button id="draw-polygon" class="btn btn-tool">
            <span class="icon-badge"><i class="bi bi-square"></i></span>绘制面
          </button>
          <button id="modify-feature" class="btn btn-tool">
            <span class="icon-badge"><i class="bi bi-pencil-square"></i></span>编辑要素
          </button>
          <button id="delete-feature" class="btn btn-tool">
            <span class="icon-badge"><i class="bi bi-trash"></i></span>删除要素
          </button>
        </div>
      </div>

      <div class="tool-section">
        <h5><i class="bi bi-layers"></i> 图层管理</h5>
        <div class="form-check form-switch">
          <input class="form-check-input" type="checkbox" id="toggle-base-layer" checked>
          <label class="form-check-label" for="toggle-base-layer">底图可见</label>
        </div>
        <div class="form-check form-switch mt-2">
          <input class="form-check-input" type="checkbox" id="toggle-drawing-layer" checked>
          <label class="form-check-label" for="toggle-drawing-layer">绘图图层可见</label>
        </div>
      </div>

      <div class="tool-section export-section">
        <h5><i class="bi bi-download"></i> 导出数据</h5>
        <button id="export-geojson" class="btn btn-export">
          <i class="bi bi-filetype-json"></i> 导出为GeoJSON
        </button>
      </div>
    </div>

    <!-- 地图容器 -->
    <div class="map-container">
      <div id="map"></div>

      <!-- 信息面板 -->
      <div class="info-panel">
        <h5><i class="bi bi-info-circle"></i> 要素信息</h5>
        <div class="feature-list" id="feature-list">
          <div class="text-center py-3 text-muted">暂无要素</div>
        </div>
        <div class="d-flex justify-content-between">
          <small>总计: <span id="feature-count">0</span> 个要素</small>
          <button id="clear-all" class="btn btn-sm btn-outline-danger">清除全部</button>
        </div>
      </div>

      <!-- 状态栏 -->
      <div class="status-bar">
        <div class="container d-flex justify-content-between">
          <span id="current-tool">当前工具: 导航</span>
          <span id="coordinate-display">经纬度: 0.000, 0.000</span>
        </div>
      </div>
    </div>
  </div>

  <script>
    // 封装绘图工具类
    class MapDrawTools {
      constructor(map, options = {}) {
        this.map = map;
        this.options = {
          drawingLayerName: '绘图图层',
          ...options
        };

        // 创建矢量图层
        this.vectorSource = new ol.source.Vector();
        this.vectorLayer = new ol.layer.Vector({
          source: this.vectorSource,
          name: this.options.drawingLayerName,
          style: this.createDefaultStyle()
        });
        this.map.addLayer(this.vectorLayer);

        // 初始化交互工具
        this.draw = null;
        this.modify = null;
        this.select = null;

        // 当前激活的工具
        this.activeTool = null;

        // 注册地图事件
        this.map.on('pointermove', this.handlePointerMove.bind(this));
      }

      // 创建默认样式
      createDefaultStyle() {
        return new ol.style.Style({
          image: new ol.style.Circle({
            radius: 7,
            fill: new ol.style.Fill({ color: '#4361ee' }),
            stroke: new ol.style.Stroke({
              color: '#fff', width: 2
            })
          }),
          fill: new ol.style.Fill({
            color: 'rgba(67, 97, 238, 0.2)'
          }),
          stroke: new ol.style.Stroke({
            color: '#4361ee',
            width: 3
          })
        });
      }

      // 激活绘图工具
      activateDrawing(type) {
        this.deactivateCurrentTool();

        this.draw = new ol.interaction.Draw({
          source: this.vectorSource,
          type: type,
          style: this.createActiveStyle()
        });

        this.map.addInteraction(this.draw);
        this.activeTool = this.draw;

        // 监听绘制完成事件
        this.draw.on('drawend', () => {
          this.updateFeatureList();
        });

        return this;
      }

      // 创建激活状态样式
      createActiveStyle() {
        return new ol.style.Style({
          image: new ol.style.Circle({
            radius: 7,
            fill: new ol.style.Fill({ color: '#e63946' }),
            stroke: new ol.style.Stroke({
              color: '#fff', width: 2
            })
          }),
          fill: new ol.style.Fill({
            color: 'rgba(230, 57, 70, 0.2)'
          }),
          stroke: new ol.style.Stroke({
            color: '#e63946',
            width: 3
          })
        });
      }

      // 激活编辑工具
      activateModify() {
        this.deactivateCurrentTool();

        this.modify = new ol.interaction.Modify({
          source: this.vectorSource
        });

        this.map.addInteraction(this.modify);
        this.activeTool = this.modify;

        // 监听修改完成事件
        this.modify.on('modifyend', () => {
          this.updateFeatureList();
        });

        return this;
      }

      // 激活删除工具
      activateDelete() {
        this.deactivateCurrentTool();

        this.select = new ol.interaction.Select({
          layers: [this.vectorLayer],
          condition: ol.events.condition.singleClick
        });

        this.map.addInteraction(this.select);
        this.activeTool = this.select;

        // 监听选择事件
        this.select.on('select', (event) => {
          event.selected.forEach(feature => {
            this.vectorSource.removeFeature(feature);
          });
          this.updateFeatureList();
        });

        return this;
      }

      // 停用当前工具
      deactivateCurrentTool() {
        if (this.activeTool) {
          this.map.removeInteraction(this.activeTool);
          this.activeTool = null;
        }

        if (this.select) {
          this.map.removeInteraction(this.select);
          this.select = null;
        }
      }

      // 清空所有要素
      clearAllFeatures() {
        this.vectorSource.clear();
        this.updateFeatureList();
      }

      // 导出为GeoJSON
      exportToGeoJSON() {
        const format = new ol.format.GeoJSON();
        const features = this.vectorSource.getFeatures();
        return format.writeFeatures(features);
      }

      // 更新要素列表
      updateFeatureList() {
        const featureList = document.getElementById('feature-list');
        const featureCount = document.getElementById('feature-count');
        const features = this.vectorSource.getFeatures();

        if (features.length === 0) {
          featureList.innerHTML = '<div class="text-center py-3 text-muted">暂无要素</div>';
          featureCount.textContent = '0';
          return;
        }

        let html = '';
        features.forEach((feature, index) => {
          const geom = feature.getGeometry();
          const type = geom.getType();
          const typeMap = {
            'Point': '点',
            'LineString': '线',
            'Polygon': '面'
          };

          html += `
                    <div class="feature-item">
                        <div>
                            <strong>要素 ${index + 1}</strong>
                            <div class="text-muted small">${typeMap[type] || type}</div>
                        </div>
                        <div class="feature-actions">
                            <i class="bi bi-zoom-in" data-id="${feature.ol_uid}" title="定位"></i>
                            <i class="bi bi-trash" data-id="${feature.ol_uid}" title="删除"></i>
                        </div>
                    </div>
                    `;
        });

        featureList.innerHTML = html;
        featureCount.textContent = features.length;

        // 添加事件监听
        featureList.querySelectorAll('.bi-zoom-in').forEach(icon => {
          icon.addEventListener('click', (e) => {
            const featureId = e.target.getAttribute('data-id');
            const feature = this.vectorSource.getFeatureByUid(featureId);
            if (feature) {
              const extent = feature.getGeometry().getExtent();
              this.map.getView().fit(extent, {
                padding: [100, 100, 100, 100],
                duration: 500
              });
            }
          });
        });

        featureList.querySelectorAll('.bi-trash').forEach(icon => {
          icon.addEventListener('click', (e) => {
            const featureId = e.target.getAttribute('data-id');
            const feature = this.vectorSource.getFeatureByUid(featureId);
            if (feature) {
              this.vectorSource.removeFeature(feature);
              this.updateFeatureList();
            }
          });
        });
      }

      // 处理鼠标移动事件
      handlePointerMove(event) {
        const coordinate = event.coordinate;
        const lonLat = ol.proj.toLonLat(coordinate);

        document.getElementById('coordinate-display').textContent =
          `经纬度: ${lonLat[0].toFixed(6)}, ${lonLat[1].toFixed(6)}`;
      }

      // 设置当前工具状态
      setToolStatus(toolName) {
        document.getElementById('current-tool').textContent =
          `当前工具: ${toolName}`;
      }
    }

    // 页面加载完成后初始化
    document.addEventListener('DOMContentLoaded', () => {
      // 创建地图
      const map = new ol.Map({
        target: 'map',
        layers: [
          new ol.layer.Tile({
            source: new ol.source.OSM(),
            name: '底图'
          })
        ],
        view: new ol.View({
          center: ol.proj.fromLonLat([116.4074, 39.9042]), // 北京市中心
          zoom: 10
        })
      });

      // 创建绘图工具实例
      const drawTools = new MapDrawTools(map, {
        drawingLayerName: '用户绘图'
      });

      // 工具按钮事件绑定
      document.getElementById('draw-point').addEventListener('click', () => {
        drawTools.activateDrawing('Point');
        drawTools.setToolStatus('绘制点');
        highlightActiveButton('draw-point');
      });

      document.getElementById('draw-line').addEventListener('click', () => {
        drawTools.activateDrawing('LineString');
        drawTools.setToolStatus('绘制线');
        highlightActiveButton('draw-line');
      });

      document.getElementById('draw-polygon').addEventListener('click', () => {
        drawTools.activateDrawing('Polygon');
        drawTools.setToolStatus('绘制面');
        highlightActiveButton('draw-polygon');
      });

      document.getElementById('modify-feature').addEventListener('click', () => {
        drawTools.activateModify();
        drawTools.setToolStatus('编辑要素');
        highlightActiveButton('modify-feature');
      });

      document.getElementById('delete-feature').addEventListener('click', () => {
        drawTools.activateDelete();
        drawTools.setToolStatus('删除要素');
        highlightActiveButton('delete-feature');
      });

      document.getElementById('clear-all').addEventListener('click', () => {
        if (confirm('确定要清除所有要素吗？')) {
          drawTools.clearAllFeatures();
        }
      });

      document.getElementById('export-geojson').addEventListener('click', () => {
        const geojson = drawTools.exportToGeoJSON();
        const blob = new Blob([geojson], { type: 'application/json' });
        const url = URL.createObjectURL(blob);

        const a = document.createElement('a');
        a.href = url;
        a.download = 'map-features.geojson';
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);

        alert('GeoJSON文件已导出！');
      });

      // 图层可见性切换
      document.getElementById('toggle-base-layer').addEventListener('change', (e) => {
        map.getLayers().forEach(layer => {
          if (layer.get('name') === '底图') {
            layer.setVisible(e.target.checked);
          }
        });
      });

      document.getElementById('toggle-drawing-layer').addEventListener('change', (e) => {
        map.getLayers().forEach(layer => {
          if (layer.get('name') === '用户绘图') {
            layer.setVisible(e.target.checked);
          }
        });
      });

      // 高亮活动按钮
      function highlightActiveButton(activeId) {
        const toolButtons = document.querySelectorAll('.btn-tool');
        toolButtons.forEach(btn => {
          btn.classList.remove('active');
        });
        if (activeId) {
          document.getElementById(activeId).classList.add('active');
        }
      }

      // 初始更新要素列表
      drawTools.updateFeatureList();
    });
  </script>
</body>

</html>